<?php

# -- DÉBUT NOTE DE LICENCE -------------------------------------
#
# Ce fichier fait partie de Clicpage.
#
# Copyright (c) 2010 Francois (www.clicpage.net)
# Distribué selon les termes de la licence GPL version 2.0.
# Consultez le fichier LICENCE ou
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
#
# -- FIN NOTE DE LICENCE ---------------------------------------

/**
@ingroup CP_CORE
@nosubgrouping
@brief Classe du site Clicpage.

Une instance de cpSite est accessible par la propriété $site de cpCore.
*/
class cpSite
{
	
	
	
	
	
	protected $core;			///< <b>cpCore</b>	Instance de cpCore
	protected $proprietes;		///< <b>array</b>	Propriétés du site
	
	
	
	
	
	/// @name Initialisation
	//@{
	
	
	/**
	Initialise une instance de cpSite.
	
	@param	core		<b>cpCore</b>		Référence à cpCore
	*/
	public function __construct($core)
	{
		$this->core = &$core;
	}	
	//@}
	
	
	
	
	
	/// @name Propriétés du site
	//@{
	

	/**
	Retourne un tableau contenant toutes les propriétés du site
	(issues de la table cp_site).
	
	@return	<b>array</b>	Propriétés du site
	*/
	public function getProprietes()
	{
		
		$rs = $this->core->con->select(
			"SELECT propriete,module,valeur FROM ".$this->core->prefix."site "
		);
		
		$proprietes = array();
		
		while($rs->fetch()) {
			$proprietes[$rs->module][$rs->propriete] = $rs->valeur;
		}
		
		$this->proprietes = $proprietes;
		
		return $this->proprietes;
		
	}
	
	
	/**
	Retourne la propriété <var>$propriete</var> du module
	<var>$module</var>, issue de la table 'cp_site'.
	
	@param	propriete	<b>string</b>		Identifiant de la propriété à retourner
	@param	module		<b>string</b>		Module de la propriété à retourner
	
	@return	<b>string</b>	Valeur de la propriété
	*/
	public function getPropriete($propriete, $module='core')
	{
		
		if(!is_array($this->proprietes)) {
			$this->getProprietes();
		}
		
		return $this->proprietes[$module][$propriete];

	}
	
	
	/**
	Définit la propriété <var>$propriete</var> du module
	<var>$module</var> (dans la table 'cp_site').
	
	@param	propriete	<b>string</b>		Identifiant de la propriété à retourner
	@param	module		<b>string</b>		Module de la propriété à retourner
	
	@return	<b>string</b>	Valeur de la propriété
	*/
	public function definirPropriete($propriete, $valeur, $module='core')
	{
		
		
		$counter = $this->core->con->select(
			'SELECT count(valeur) '.
			'FROM '.$this->core->prefix.'site '.
			"WHERE propriete = '".$this->core->con->escape($propriete)."' ".
			"AND module = '".$this->core->con->escape($module)."'"
		);
		
		$cur = $this->core->con->openCursor($this->core->prefix.'site');
		$cur->propriete = $propriete;
		$cur->module = $module;
		$cur->valeur = $valeur;
		
		# --COMPORTEMENT-- coreAvantDefinirPropriete
		$this->core->executerComportement('coreAvantDefinirPropriete', $cur);
		
		if($counter->f(0) == 0) {
			$cur->insert();
		} else {
			$cur->update("WHERE propriete = '".$this->core->con->escape($propriete)."' ".
				"AND module = '".$this->core->con->escape($module)."'");
		}
		
		# --COMPORTEMENT-- coreApresDefinirPropriete
		$this->core->executerComportement('coreApresDefinirPropriete', $cur);
		
	}
	
	
	/**
	Définit le statut de l'antispam <var>$antispam</var>.
	
	@param	antispam	<b>string</b>		Identifiant de l'antispam
	@param	statut		<b>boolean</b>		Statut de l'antispam
	*/
	public function setStatutAntispam($antispam, $statut)
	{
		
		# --COMPORTEMENT-- coreAvantDefinirStatutAntispam
		$this->core->executerComportement('coreAvantDefinirStatutAntispam', $antispam, $statut);
		
		$data = $this->getPropriete('antispams', 'core');
		
		if(empty($data)) {
			$data = array();
		} else {
			$data = unserialize($data);
		}
		
		$data[$antispam] = (boolean) $statut;		
		$this->definirPropriete('antispams', serialize($data), 'core');
		
		# --COMPORTEMENT-- coreApresDefinirStatutAntispam
		$this->core->executerComportement('coreApresDefinirStatutAntispam', $antispam, $statut);
		
	}
	//@}
	
	
	
		
	
	/// @name Gestion des catégories
	//@{
	
	
	/**
	Retourne des catégories. <var>$params</var> est un
	tableau qui peut prendre les paramètres suivants:
	
	- cat_id: filtre sur l'identifiant des catégories
	- parent_cat_id: filtre sur l'identifiant de la catégorie <i>directement</i> parente
	- cat_url: filtre sur l'URL de la catégorie
	- home_page_id: filtre sur l'identifiant de la page d'accueil de la catégorie
	- operateur: opérateur (CP_OPERATEUR_AND, CP_OPERATEUR_OR) à placer entre les conditions
	
	@param	params	<b>array</b>	Paramètres
	@param	type	<b>integer</b>	Type de résultat à retourner: CP_GET_RS, CP_GET_STATIC ou CP_GET_COMPTEUR
	
	@return	<b>recordset</b>	Catégories
	*/
	public function getCategories($params=array(), $type=CP_GET_RS)
	{
		
		$champs = array(
			array('cat_id', 'C', CP_INTEGER),
			array('parent_cat_id', 'C', CP_INTEGER),
			array('cat_url', 'C', CP_STRING),
			array('cat_titre', 'C', CP_STRING),
			array('home_page_id', 'C', CP_INTEGER)
		);
		
		$from = $this->core->prefix.'categories C';
		
		# --COMPORTEMENT-- coreAvantGetCategories
		$this->core->executerComportement('coreAvantGetCategories', array(&$champs, &$params, &$from, &$type));
		
		$strReq = $this->core->selectSql(
			$champs,		# Champs
			$params,		# Valeurs
			$from,			# From
			$type			# Type
		);
		
		$rs = $this->core->con->select($strReq);
		
		# --COMPORTEMENT-- coreApresGetCategories
		$this->core->executerComportement('coreApresGetCategories', $rs, array(&$champs, &$params, &$from, &$type));
		
		return $this->core->selectReturn($type, $rs, 'cpCategorie');
		
	}
	
	
	/**
	Retourne un <b>array</b> contenant une liste de toutes les
	catégories du site prêt à être utilisé par 'form::combo'.
	
	@return	<b>array</b>
	*/
	public function getComboCategories($start=0, $chemin = '')
	{
		
		$comboCategories = array();
		
		$categories = $this->getCategories(array('parent_cat_id' => (integer) $start), CP_GET_RS);

		while($categories->fetch())
		{
			
			# Catégorie
			$comboCategories[html::escapeHTML($chemin.$categories->cat_titre.'/')] = (integer) $categories->cat_id;
			
			# Sous-catégories
			$comboCategories = array_merge($comboCategories, $this->getComboCategories($categories->cat_id, html::escapeHTML($chemin.$categories->cat_titre.'/')));
				
		}
		
		return $comboCategories;
		
	}
	
	
	/**
	Vérifie si une catégorie existe.
	
	@param	cat_id	<b>integer</b>	Identifiant de la catégorie à vérifier
	
	@return	<b>boolean</b>
	*/
	public function categorieExiste($cat_id) 
	{
		
		$rs = $this->core->con->select(
			'SELECT cat_id FROM '.$this->core->prefix.'categories '.
			'WHERE cat_id = '.(integer) $cat_id
		);
		
		if($rs->isEmpty()) {
			return false;
		}
		
		return true;
		
	}
	
	
	/**
	Vérifie la validité des données d'un curseur de catégorie.
	
	@param	cur			<b>cursor</b>	Curseur de la catégorie
	@param	nouveau		<b>bool</b>		Indique s'il s'agit d'une nouvelle catégorie
	@param	cat_id		<b>integer</b>	Identifiant de la catégorie, si ce n'est pas une nouvelle catégorie
	*/
	public function verifierDonneesCategorie($cur, $nouveau=false, $cat_id=null)
	{

		if($cat_id != 1 && $cur->isField('parent_cat_id'))
		{

			# Vérifier l'existence du dossier parent
			$parent = $this->core->site->getCategories(array('cat_id' => $cur->parent_cat_id), CP_GET_STATIC);
			
			if($parent->isEmpty()) {
				throw new cpErreur(__('Cette clé n\'existe pas dans la base de données.'), 'parent_cat_id', 103);
			}
			
			if(!$nouveau)
			{
				$arb = array();
				$arborescence = $parent->getArborescence();
				foreach($arborescence as $cat) {
					$arb[] = $cat->cat_id;
				}
				$arb[] = $parent->cat_id;
				
				# Vérifier qu'on ne place pas le dossier à l'intérieur de lui-même:
				if(in_array($cat_id,$arb)) {
					throw new cpErreur(__('Vous ne pouvez pas déplacer le dossier à l\'intérieur de lui-même.'), 'parent_cat_id', 120);
				}
			}
		}

		# Le titre ne doit pas être vide
		if($cur->isField('cat_titre') && $cur->cat_titre == '') {
			throw new cpErreur(__('Vous devez indiquer un titre.'), 'cat_titre', 101);
		}
		
		if($cur->isField('cat_url'))
		{
			# Nettoyage de l'URL
			$cur->cat_url = path::clean(text::str2URL($cur->cat_url));
			
			# L'URL ne doit pas commencer par "medias" ou "cp"
			if(	preg_match('#^medias(/.+)?$#', $cur->cat_url) || $cur->cat_url == 'medias' ||
				preg_match('#^cp(/.+)?$#', $cur->cat_url) || $cur->cat_url == 'cp')
			{
				throw new cpErreur(__('L\'URL ne peut pas commencer par "medias" ou "cp".'), 'cat_url', 110);
			}
		}

	}
	
	
	/**
	Crée une nouvelle catégorie.
	
	<var>$cur</var> contient:
	- parent_cat_id
	- cat_url
	- cat_titre
	
	@param	cur	<b>cursor</b>	Curseur de la catégorie
	
	@return	<b>cursor</b>	Curseur de la catégorie
	*/
	public function creerCategorie($cur)
	{
		
		$rs = $this->core->con->select(
			'SELECT MAX(cat_id) '.
			'FROM '.$this->core->prefix.'categories'
		);
		
		$cur->cat_id = (integer) $rs->f(0) + 1;
		
		$rs = $this->core->con->select(
			'SELECT MAX(page_id) '.
			'FROM '.$this->core->prefix.'pub_pages'
		);
		
		$cur->home_page_id = (integer) $rs->f(0) + 1;
		
		$this->verifierDonneesCategorie($cur, true);
		
		# --COMPORTEMENT-- coreAvantCreerCategorie
		$this->core->executerComportement('coreAvantCreerCategorie', $cur);
		
		$cur->insert();
		
		$curPage = $this->core->con->openCursor($this->core->prefix.'temp_pages');
		
		$curPage->cat_id = (integer) $cur->cat_id;
		$curPage->page_generateur = 'contenu';
		$curPage->page_theme = '_default';
		$curPage->page_statut = CP_STATUT_ONLINE;
		$curPage->page_langue = $this->core->auth->utilisateur->utilisateur_langue;
		$curPage->page_url = $cur->cat_url;
		$curPage->page_titre = $cur->cat_titre;
		$curPage->page_entete = '';
		$curPage->page_entete_xhtml = '';
		$curPage->page_contenu = '';
		$curPage->page_contenu_xhtml = '';
		$curPage->page_commentaires = $this->core->site->getPropriete('page_commentaires');
		$curPage->page_date = dt::str('%Y-%m-%d %H:%M:%S');
		$curPage->page_date_modification = dt::str('%Y-%m-%d %H:%M:%S');
		$curPage->utilisateur_id = (integer) $this->core->auth->getUtilisateurID();
		$curPage->modification_utilisateur_id = (integer) $this->core->auth->getUtilisateurID();
		$curPage->page_mots = '';
		
		$this->creerPage($curPage, $cur->home_page_id);
		
		# --COMPORTEMENT-- coreApresCreationCategorie
		$this->core->executerComportement('coreApresCreerCategorie', $cur, $curPage);
		
		return $cur;
		
	}
	//@}
	




	/// @name Gestion des pages
	//@{
	
	
	/**
	Retourne des pages. <var>$params</var> est un
	tableau qui peut prendre les paramètres suivants:
	
	Filtres sur les champs
	- page_id: (integer) filtre sur l'identifiant de la page
	- cat_id: (integer) filtre sur l'identifiant de la catégorie de la page
	- page_generateur: filtre sur l'identifiant du générateur de page
	- page_theme: filtre sur l'identifiant du thème
	- page_statut: filtre sur le statut de la page
	- page_modifiee: filtre sur le champ page_modifiee 
		(CP_PAGEMODIFIEE_OUI indique l'existence d'un brouillon, CP_PAGEMODIFIEE_NON indique qu'il n'y a pas de brouillon)
	- page_langue: filtre sur la langue de la page
	- page_url: filtre sur l'URL de la page
	- page_titre: filtre sur le titre de la page
	- page_entete, page_entete_xhtml
	- page_contenu, page_contenu_xhtml
	- page_commentaires: filtre sur le fonctionnement des commentaires pour la page
		(CP_COMMENTAIRES_ACTIFS, CP_COMMENTAIRES_DESACTIVES, CP_COMMENTAIRES_PERMISSION, CP_COMMENTAIRES_READONLY)
	- page_acces: filtre sur les restrictions d'accès de la page
		(CP_ACCES_TOUS, CP_ACCES_PERMISSION)
	- page_date: filtre sur la date de création de la page
	- page_date_modification: filtre sur la date de modification de la page
	- utilisateur_id: filtre sur l'identifiant de l'auteur de la page
	- modification_utilisateur_id: filtre sur l'identifiant du dernier utilisateur ayant modifié la page
	- page_mots: filtre sur les mots contenus sur la page
	
	Autres
	- rechercher: mots-clés séparés par des espaces
	- rechercher_admin: mots-clés séparés par des espaces, recherche aussi dans les brouillons
	- tag: filtre sur les tags de la page (  array(=, !=, like, not_like)  )
	- operateur: opérateur à placer entre les conditions (CP_OPERATEUR_AND ou CP_OPERATEUR_OR)
	- order: ORDER BY...
	- limit: paramètre limite
	
	Exemples d'utilisation:
	
	Obtenir la version publiée des pages:
	<code>$this->core->site->getPages(array(), CP_GET_RS, CP_PUBLIEE);</code>
	
	Obtenir le brouillon des pages:
	<code>$this->core->site->getPages(array(), CP_GET_RS, CP_TEMPORAIRE);</code>
	* la version publiée sera retournée s'il n'y a pas de brouillon

	@param	params	<b>array</b>	Paramètres
	@param	type	<b>integer</b>	Type de résultat à retourner: CP_GET_RS, CP_GET_STATIC ou CP_GET_COMPTEUR
	@param	version	<b>integer</b>	Version de la page à retourner (CP_PUBLIEE pour la version publiée ou CP_TEMPORAIRE pour le brouillon)
	@param	table	<b>string</b>	Table sur laquelle effectuer la requête
	
	@return	<b>recordset</b>	Pages
	*/
	public function getPages($params=array(), $type=CP_GET_RS, $version=CP_PUBLIEE, $table='pub_pages')
	{
		
		$tbl = 'P';

		$champs = array(
			array('page_id', $tbl, CP_INTEGER),
			array('cat_id', $tbl, CP_INTEGER),
			array('page_generateur', $tbl, CP_STRING),
			array('page_theme', $tbl, CP_STRING),
			array('page_statut', $tbl, CP_INTEGER),
			array('page_modifiee', $tbl, CP_INTEGER),
			array('page_langue', $tbl, CP_STRING),
			array('page_url', $tbl, CP_STRING),
			array('page_titre', $tbl, CP_STRING),
			array('page_entete', $tbl, CP_STRING),
			array('page_entete_xhtml', $tbl, CP_STRING),
			array('page_contenu', $tbl, CP_STRING),
			array('page_contenu_xhtml', $tbl, CP_STRING),
			array('page_options',$tbl,CP_STRING),
			array('page_commentaires', $tbl, CP_INTEGER),
			array('page_acces', $tbl, CP_INTEGER),
			array('page_acces_details', $tbl, CP_STRING),
			array('page_date', $tbl, CP_DATE),
			array('page_date_modification', $tbl, CP_DATE),
			array('utilisateur_id', $tbl, CP_INTEGER),
			array('modification_utilisateur_id', $tbl, CP_INTEGER),
			array('page_mots', $tbl, CP_STRING)
		);

		$from = $this->core->prefix.$table.' P';		
		
		if(!empty($params['rechercher'])) {
			
			$mots = text::splitWords($params['rechercher']);
			$nb = count($mots);

			for($i=0; $i<$nb; $i++) {
				$mots[$i] = '%'.$mots[$i].'%';
			}
			
			if(!empty($mots)) {
				$params['page_mots'] = array('like' => $mots);
			}
		}
		
		
		if(!empty($params['rechercher_admin'])) {
			
			$mots = text::splitWords($params['rechercher_admin']);
			$nb = count($mots);

			for($i=0; $i<$nb; $i++) {
				$mots[$i] = '%'.$mots[$i].'%';
			}

			$valeurs = array('like' => $mots);
			
			if(!empty($valeurs['like']))
			{
				
				$params['conditions_supp'] = array('( '.
					'('.$this->core->conditionSql('page_mots', $valeurs, CP_STRING, 'P').") OR\n".
					'('.$this->core->conditionSql('page_mots', $valeurs, CP_STRING, 'T').')'.
				' )');
				
				$from =
				$this->core->prefix."pub_pages P\n".
				'LEFT OUTER JOIN '.$this->core->prefix."temp_pages T ON P.page_id = T.page_id";
			
			}
			
		}
		
		
		if(!empty($params['tag']))
		{
			if(!is_array($params['tag'])) {
				$params['tag'] = array('=' => array($params['tag']));
			}
			
			if(is_array($params['tag']['='])) {
				foreach($params['tag']['='] as $tag) {
					$params['conditions_supp'][] = 'P.page_id IN (SELECT page_id FROM '.$this->core->prefix."tags WHERE tag_texte = '".$this->core->con->escape($tag)."')";
				}
			}
			if(is_array($params['tag']['!='])) {
				foreach($params['tag']['!='] as $tag) {
					$params['conditions_supp'][] = 'P.page_id NOT IN (SELECT page_id FROM '.$this->core->prefix."tags WHERE tag_texte = '".$this->core->con->escape($tag)."')";
				}
			}
			if(is_array($params['tag']['like'])) {
				foreach($params['tag']['like'] as $tag) {
					$params['conditions_supp'][] = 'P.page_id IN (SELECT page_id FROM '.$this->core->prefix."tags WHERE tag_texte LIKE '".$this->core->con->escape($tag)."')";
				}
			}
			if(is_array($params['tag']['not_like'])) {
				foreach($params['tag']['not_like'] as $tag) {
					$params['conditions_supp'][] = 'P.page_id NOT IN (SELECT page_id FROM '.$this->core->prefix."tags WHERE tag_texte LIKE '".$this->core->con->escape($tag)."')";
				}
			}
			
		}		
		
		# --COMPORTEMENT-- coreAvantGetPages
		$this->core->executerComportement('coreAvantGetPages', array(&$champs, &$params, &$from, &$type, &$version, &$table));
		
		$strReq = $this->core->selectSql(
			$champs,		# Champs
			$params,		# Valeurs
			$from,			# From
			$type			# Type
		);

		$rs = $this->core->con->select($strReq);
		
		# --COMPORTEMENT-- coreApresGetPages
		$this->core->executerComportement('coreApresGetPages', array(&$champs, &$params, &$from, &$type, &$version, &$table), $rs);
		
		return $this->core->selectReturn($type, $rs, 'cpPage', $version);
	
	}
	
	
	/**
	Vérifie la validité des données d'un curseur de page.
	
	@param	cur			<b>cursor</b>	Curseur de la page
	@param	nouveau		<b>bool</b>		Indique s'il s'agit d'une nouvelle page
	@param	page_id		<b>integer</b>	Identifiant de la page, si ce n'est pas une nouvelle page
	*/
	public function verifierDonneesPage($cur, $nouveau=false, $page_id=null)
	{

		# Le titre ne doit pas être vide
		if($cur->isField('page_titre') && $cur->page_titre == '') {
			throw new cpErreur(__('Vous devez indiquer un titre.'), 'page_titre', 101);
		}
		
		if($cur->isField('page_url'))
		{
			# Enlever les "/" en début d'URL
			$cur->page_url = preg_replace('/^\/+/', '', $cur->page_url);
	
			# L'URL ne doit pas être vide
			if($cur->page_url == '') {
				throw new cpErreur(__('Vous devez indiquer une URL.'), 'page_url', 101);
			}
			
			# L'URL ne doit pas commencer par "medias" ou "cp"
			if(	preg_match('#^medias(/.+)?$#', $cur->page_url) || $cur->page_url == 'medias' ||
				preg_match('#^cp(/.+)?$#', $cur->page_url) || $cur->page_url == 'cp')
			{
				throw new cpErreur(__('L\'URL ne peut pas commencer par "medias" ou "cp".'), 'page_url', 110);
			}
			
			# Vérifier la présence de caractères indésirables dans l'URL
			$count = 0;
			$test = str_replace(array(',', '?','%','&','#','=','\\'),'',$cur->page_url, $count);
			if($count != 0) {
				throw new cpErreur(sprintf(__('Les caractères suivants sont interdits: %s'), ', ? % & # = \\'), 'page_url', 110);
			}
			
			# Enlever les barres obliques en fin d'URL
			$cur->page_url = preg_replace('#(/*?)$#', '', $cur->page_url);
			
			# L'URL doit être unique
			$params = array('page_url' => $cur->page_url);
			if(!$nouveau) {
				$params['page_id'] = array('!=' => array((integer) $page_id));
			}		
			$urlCheck = $this->getPages($params, CP_GET_COMPTEUR);
			if($urlCheck->f(0) != 0) {
				throw new cpErreur(__('Cette URL est déjà utilisée.'), 'page_url', 102);
			}
		}
		
		# Vérification du générateur de page
		if($cur->isField('page_generateur') && !array_key_exists($cur->page_generateur, $this->core->getGenerateurs())) {
			throw new cpErreur(__('Donnée invalide'), 'page_generateur', 103);
		}
		
		# Vérification du thème
		if($cur->isField('page_theme') && $cur->page_theme != '_default') {
			$this->core->chargerThemes();
			if(!array_key_exists($cur->page_theme, $this->core->themes)) {
				throw new cpErreur(__('Donnée invalide'), 'page_theme', 103);
			}		
		}

		# Vérification du statut
		if($cur->isField('page_statut') && !in_array($cur->page_statut, array(CP_STATUT_ONLINE, CP_STATUT_OFFLINE))) {
			throw new cpErreur(__('Donnée invalide'), 'page_statut', 103);
		}

		# Vérification de la langue
		if($cur->isField('page_langue') && !array_key_exists($cur->page_langue, $this->core->getLangues())) {
			throw new cpErreur(__('Donnée invalide'), 'page_langue', 103);
		}
		
		# Vérification de page_commentaires
		if($cur->isField('page_commentaires') && !in_array($cur->page_commentaires, array(CP_COMMENTAIRES_ACTIFS, CP_COMMENTAIRES_DESACTIVES, CP_COMMENTAIRES_PERMISSION, CP_COMMENTAIRES_READONLY))) {
			throw new cpErreur(__('Donnée invalide'), 'page_commentaires', 103);
		}
		
		# Vérification de page_acces
		if($cur->isField('page_acces') && !in_array($cur->page_acces, array(CP_ACCES_TOUS, CP_ACCES_PERMISSION, CP_ACCES_MOT_DE_PASSE))) {
			throw new cpErreur(__('Donnée invalide'), 'page_acces', 103);
		}
		
		if($cur->page_acces == CP_ACCES_MOT_DE_PASSE)
		
		# Date de création valide
		if($cur->isField('page_date') && !( $date = strtotime($cur->page_date ))) {
			throw new cpErreur(__('Date invalide'), 'page_date', 104);
		}
		
	}
	
	
	/**
	Crée une nouvelle page.
	
	<var>$cur</var> contient:
	- cat_id
	- page_generateur
	- page_theme
	- page_url
	- page_titre
	
	@param	cur			<b>cursor</b>	Curseur de page
	@param	page_id		<b>array</b>	Permet de forcer l'identifiant de la nouvelle page
	
	@return	<b>cursor</b>	Curseur de la nouvelle page
	*/
	public function creerPage($cur, $page_id = NULL)
	{

		if(is_null($page_id) || !is_numeric($page_id)) {
			
			$rs = $this->core->con->select(
				'SELECT MAX(page_id) '.
				'FROM '.$this->core->prefix.'pub_pages'
			);
	
			$cur->page_id = (integer) $rs->f(0) + 1;
			
		} else {
			$cur->page_id = (integer) $page_id;
		}
		
		$this->verifierDonneesPage($cur);
		
		# On laisse le générateur de page faire les modifications au curseur
		$nomClasse = 'generateur'.$cur->page_generateur;
		$generateur = new $nomClasse($cur, $this->core);
		$generateur->curseurNouvellePage($cur);
		
		$cur->page_modifiee					= CP_PAGEMODIFIEE_OUI;
		$cur->page_options = serialize($cur->page_options);
		
		$curPub = $this->core->con->openCursor($this->core->prefix.'pub_pages');
		
		$curPub->page_id 					= $cur->page_id;
		$curPub->cat_id 					= $cur->cat_id;
		$curPub->page_generateur 			= $cur->page_generateur;
		$curPub->page_theme 				= $cur->page_theme;
		$curPub->page_statut 				= CP_STATUT_OFFLINE;
		$curPub->page_modifiee 				= CP_PAGEMODIFIEE_OUI;
		$curPub->page_langue 				= $cur->page_langue;
		$curPub->page_url 					= $cur->page_url;
		$curPub->page_titre 				= $cur->page_titre;
		$curPub->page_entete 				= $cur->page_entete;
		$curPub->page_entete_xhtml 			= $cur->page_entete_xhtml;
		$curPub->page_contenu 				= $cur->page_contenu;
		$curPub->page_contenu_xhtml 		= $cur->page_contenu_xhtml;
		$curPub->page_options 				= $cur->page_options;
		$curpub->page_commentaires 			= $cur->page_commentaires;
		$curPub->page_date 					= $cur->page_date;
		$curPub->page_date_modification 	= $cur->page_date_modification;
		$curPub->utilisateur_id 			= $cur->utilisateur_id;
		$curPub->modification_utilisateur_id = $cur->modification_utilisateur_id;
		$curPub->page_mots 					= $cur->page_mots;
		
		# --COMPORTEMENT-- coreAvantCreerPage
		$this->core->executerComportement('coreAvantCreerPage', $cur, $curPub, $page_id);
		
		# Insertion dans la base de données
		$curPub->insert();
		$cur->insert();
		
		# --COMPORTEMENT-- coreApresCreerPage
		$this->core->executerComportement('coreApresCreerPage', $cur, $curPub, $page_id);
		
		return $cur;
		
	}
	
	
	/**
	Retourne des tags.
	
	<var>$params</var> est un tableau qui peut prendre
	les paramètres suivants:
	
	- page_enligne: (boolean) rechercher des tags associés à des pages en ligne
	- page_langue: (string) rechercher des tags associés à des pages dans cette langue
	- tag_texte: filtre sur la valeur du tag
	- order: ORDER BY...
	- limit: paramètre limite
	
	Pour <var>$type</var> = CP_GET_RS:
	- nb: (boolean) ajouter un champ indiquant le nombre de fois que le tag est utilisé
	
	Pour <var>$type</var> = CP_GET_COMPTEUR:
	- distinct: (boolean) compter les tags distincts
	
	@param	params		<b>array</b>	Paramètres
	@param	type		<b>integer</b>	Type de résultat à retourner: CP_GET_RS ou CP_GET_COMPTEUR
		
	@return	<b>recordset</b>	Tags
	*/
	public function getTags($params=array(), $type=CP_GET_RS)
	{
		
		$strReq = 
		'SELECT ';
		
		if($type == CP_GET_COMPTEUR && $params['distinct']) {
			$strReq.='count(distinct T.tag_texte) ';
		} elseif($type == CP_GET_COMPTEUR) {
			$strReq.='count(T.tag_texte) ';
		} else {
			$strReq.= $params['nb'] ?
				'T.tag_texte, count(T.tag_texte) as nb ' : 'T.tag_texte ';
		}
		
		$strReq.=
		'FROM '.$this->core->prefix.'tags T ';
		
		if($params['page_enligne'] || $params['page_langue']) {
			$strReq.=
			'INNER JOIN '.$this->core->prefix.'pub_pages P ON T.page_id = P.page_id ';
		}
		
		$where = array();
		
		if(!empty($params['tag_texte'])) {
			$where[] = $this->core->conditionSql('tag_texte', $params['tag_texte'], CP_STRING, 'T');
		}
		
		$cond = false;
		
		if(!empty($where)) {
			$params['operateur'] = in_array($params['operateur'], array(CP_OPERATEUR_AND, CP_OPERATEUR_OR)) ?
				$params['operateur'] : CP_OPERATEUR_AND;			
			$strReq.='WHERE ('.implode($params['operateur'], $where).') ';
			$cond = true;
		}
		
		if($params['page_enligne'])
		{
			$strReq.= $cond ? 'AND ' : 'WHERE ';
			$strReq.= 'P.page_statut = '.CP_STATUT_ONLINE.' ';
			$cond = true;
		}
		
		if($params['page_langue'])
		{
			$strReq.= $cond ? 'AND ' : 'WHERE ';
			$strReq.= "P.page_langue = '".$this->core->con->escape($params['page_langue'])."' ";
			$cond = true;
		}

		if($type != CP_GET_COMPTEUR) {
			$strReq.= 'GROUP BY T.tag_texte ';
		}
		
		# ORDER
		if ($type != CP_GET_COMPTEUR && !empty($params['order'])) {
			$strReq .= 'ORDER BY '.$this->core->con->escape($params['order'])."\n";
		}
	
		# LIMIT
		if ($type != CP_GET_COMPTEUR && !empty($params['limit'])) {
			$strReq.= $this->core->con->limit($params['limit'])."\n";
		}
		
		$rs = $this->core->con->select($strReq);
		
		return $rs;
		
	}
	//@}
	




	/// @name Gestion des commentaires
	//@{
	
	
	/**
	Retourne des commentaires.
	
	<var>$params</var> est un tableau qui peut prendre
	les paramètres suivants:
	
	- commentaire_id: filtre sur l'identifiant du commentaire
	- page_id: filtre sur la page à laquelle le commentaire est associé
	- commentaire_contexte: filtre sur le champ commentaire_contexte
	- commentaire_statut: filtre sur le statut du commentaire (CP_COMMENTAIRE_APPROUVE, CP_COMMENTAIRE_ATTENTE, CP_COMMENTAIRE_SPAM)
	- utilisateur_id: filtre sur l'identifiant de l'utilisateur ayant écrit le commentaire
	- commentaire_ip: filtre sur l'adresse IP de l'auteur du commentaire
	- commentaire_auteur: filtre sur le nom de l'auteur du commentaire
	- commentaire_email: filtre sur l'email de l'auteur du commentaire
	- commentaire_titre: filtre sur le titre du commentaire
	- commentaire_contenu: filtre sur le contenu du commentaire
	- commentaire_date: filtre sur la date du commentaire
	- commentaire_spam: filtre sur le champ commentaire_spam
	
	- rechercher: mots-clés séparés par des espaces pour rechercher dans les champs commentaire_titre,
		commentaire_contenu et commentaire_auteur
	- rechercher_extra: (boolean) indique si la recherche doit aussi s'effectuer pour les champs
		commentaire_email et commentaire_ip
	- operateur: opérateur à placer entre les conditions (CP_OPERATEUR_AND ou CP_OPERATEUR_OR)
	- order: ORDER BY...
	- limit: paramètre limite

	@param	params	<b>array</b>	Paramètres
	@param	type	<b>integer</b>	Type de résultat à retourner: CP_GET_RS, CP_GET_STATIC ou CP_GET_COMPTEUR
	
	@return	<b>recordset</b>	Commentaires
	*/
	public function getCommentaires($params=array(), $type=CP_GET_RS)
	{

		$champs = array(
			array('commentaire_id', 'C', CP_INTEGER),
			array('page_id', 'C', CP_INTEGER),
			array('commentaire_contexte', 'C', CP_INTEGER),
			array('commentaire_statut', 'C', CP_INTEGER),
			array('utilisateur_id', 'C', CP_INTEGER),
			array('commentaire_ip', 'C', CP_STRING),
			array('commentaire_auteur', 'C', CP_STRING),
			array('commentaire_email', 'C', CP_STRING),
			array('commentaire_titre', 'C', CP_STRING),
			array('commentaire_contenu', 'C', CP_STRING),
			array('commentaire_date', 'C', CP_DATE),
			array('commentaire_spam', 'C', CP_STRING)
		);
		
		$from = $this->core->prefix."commentaires C";
		
		if($params['avec_utilisateur']) {
			
			$from.=	"\nLEFT OUTER JOIN ".$this->core->prefix."utilisateurs U ON C.utilisateur_id = U.utilisateur_id";

			$champs = array_merge($champs, array(
				array('utilisateur_superadmin', 'U', CP_INTEGER),
				array('utilisateur_login', 'U', CP_STRING),
				array('utilisateur_prenom', 'U', CP_STRING),
				array('utilisateur_nom', 'U', CP_STRING),
				array('utilisateur_pseudo', 'U', CP_STRING),
				array('utilisateur_email', 'U', CP_STRING),
				array('utilisateur_langue', 'U', CP_STRING)
			));
		}
		
		if($params['avec_page']) {
			
			$from.=	"\nLEFT OUTER JOIN ".$this->core->prefix."pub_pages P ON C.page_id = P.page_id";
			
			$champs = array_merge($champs, array(
				array('cat_id', 'P', CP_INTEGER),
				array('page_generateur', 'P', CP_STRING),
				array('page_statut', 'P', CP_INTEGER),
				array('page_modifiee', 'P', CP_INTEGER),
				array('page_langue', 'P', CP_STRING),
				array('page_url', 'P', CP_STRING),
				array('page_titre', 'P', CP_STRING),
				array('page_commentaires', 'P', CP_INTEGER),
				array('page_date', 'P', CP_DATE),
				array('page_date_modification', 'P', CP_DATE),
				array('utilisateur_id', 'P', CP_INTEGER),
				array('modification_utilisateur_id', 'P', CP_INTEGER)
			));
		}
		
		if(!empty($params['rechercher'])) {
			$motsCle = explode(' ', $params['rechercher']);
			
			$rechercheSqlT = array();
			
			foreach($motsCle as $v)
			{
				$v = trim($v);
				if(!empty($v)) {
					$v = $this->core->con->escape('%'.$v.'%');
					$sqlRecherche =
					"( C.commentaire_titre LIKE '".$v."' OR ".
					"C.commentaire_contenu LIKE '".$v."' OR ".
					"C.commentaire_auteur LIKE '".$v."' ";
					if($params['rechercher_extra']) {
						$sqlRecherche.=
							"OR C.commentaire_email LIKE '".$v."' OR ".
							"C.commentaire_ip LIKE '".$v."' ";
					}
					$sqlRecherche.=")";
					$rechercheSqlT[] = $sqlRecherche;
				}
				
			}
			
			if(!empty($rechercheSqlT)) {
				$params['conditions_supp'] = array(' ( '.implode("\n AND ", $rechercheSqlT).' ) ');
			}

		}

		# --COMPORTEMENT-- coreAvantGetCommentaires
		$this->core->executerComportement('coreAvantGetCommentaires', array(&$champs, &$params, &$from, &$type));
		
		$strReq = $this->core->selectSql(
			$champs,		# Champs
			$params,		# Valeurs
			$from,			# From
			$type			# Type
		);

		$rs = $this->core->con->select($strReq);

		# --COMPORTEMENT-- coreApresGetCommentaires
		$this->core->executerComportement('coreApresGetCommentaires', array(&$champs, &$params, &$from, &$type), $rs);
		
		return $this->core->selectReturn($type, $rs, 'cpCommentaire');

	}
	
	
	/**
	Vérifie la validité des données d'un curseur de commentaire.
	
	@param	cur			<b>cursor</b>	Curseur du commentaire
	@param	nouveau		<b>bool</b>		Indique s'il s'agit d'un nouveau commentaire
	*/
	public function verifierDonneesCommentaire($cur, $nouveau=true)
	{
		
		# Vérification de commentaire_statut
		if(($nouveau || $cur->isField('commentaire_statut')) && !in_array($cur->commentaire_statut, array(CP_COMMENTAIRE_ATTENTE, CP_COMMENTAIRE_APPROUVE, CP_COMMENTAIRE_SPAM))) {
			throw new cpErreur(__('Donnée invalide'), 'commentaire_statut', 103);
		}
		
		# Vérification de commentaire_auteur
		if(($nouveau || $cur->isField('commentaire_auteur')) && $cur->commentaire_auteur == '') {
			throw new cpErreur(__('Vous devez indiquer un pseudonyme.'), 'commentaire_auteur', 101);
		}
		
		# Vérification de commentaire_email
		if($nouveau || $cur->isField('commentaire_email'))
		{
			if($cur->commentaire_email == '') {
				throw new cpErreur(__('Vous devez indiquer une adresse électronique.'), 'commentaire_email', 101);
			}
			if(!text::isEmail($cur->commentaire_email)) {
				throw new cpErreur(__('Adresse électronique invalide.'), 'commentaire_email', 105);
			}
		}
		
		# Vérification de commentaire_titre
		if($this->getPropriete('commentaire_titre') == 1) {
			if(($nouveau || $cur->isField('commentaire_titre')) && $cur->commentaire_titre == '') {
				throw new cpErreur(__('Vous devez indiquer un titre.'), 'commentaire_titre', 101);
			}
		}
		
		# Vérification de commentaire_contenu
		if(($nouveau || $cur->isField('commentaire_contenu')) && $cur->commentaire_contenu == '') {
			throw new cpErreur(__('Vous devez indiquer un commentaire.'), 'commentaire_contenu', 101);
		}
		
		if($nouveau) {
		
			$filter = new htmlFilter;
			$wiki2xhtml = new wiki2xhtml;
			$wiki2xhtml->setOpts(array(
				'active_title' => 0,
				'active_setext_title' => 0,
				'active_hr' => 0,
				'active_lists' => 1,
				'active_quote' => 1,
				'active_pre' => 1,
				'active_empty' => 0,
				'active_auto_br' => 1,
				'active_auto_urls' => 1,
				'active_urls' => 0,
				'active_auto_img' => 0,
				'active_img' => 0,
				'active_anchor' => 0,
				'active_em' => 0,
				'active_strong' => 0,
				'active_br' => 0,
				'active_q' => 0,
				'active_code' => 1,
				'active_acronym' => 0,
				'active_ins' => 0,
				'active_del' => 0,
				'active_footnotes' => 0,
				'active_wikiwords' => 0,
				'active_macros' => 0,
				'parse_pre' => 0,
				'active_fr_syntax' => 0
			));
			
			$cur->commentaire_contenu = $wiki2xhtml->transform($cur->commentaire_contenu);
			$cur->commentaire_contenu = trim($filter->apply($cur->commentaire_contenu));
		
		}
		
	}
	
	
	/**
	Crée un nouveau commentaire.
	
	<var>$cur</var> contient:
	
	- page_id
	- commentaire_contexte
	- commentaire_statut
	- utilisateur_id (null si inconnu)
	- commentaire_ip
	- commentaire_auteur
	- commentaire_email
	- commentaire_titre (si nécessaire)
	- commentaire_contenu
	
	@param	cur			<b>cursor</b>	Curseur de commentaire
	@param	antispam	<b>bool</b>		Indique si l'ajout du commentaire doit être soumis aux filtres antispam
	
	@return	<b>cur</b>	Curseur du nouveau commentaire
	*/
	public function creerCommentaire($cur, $antispam = true)
	{
		
		$this->verifierDonneesCommentaire($cur);
		
		$cur->commentaire_spam = null;
		
		if($antispam) {
			$details = array();
			$estIndesirable = $this->core->estIndesirable($cur->commentaire_auteur,
							$cur->commentaire_email,
							$cur->commentaire_ip,
							$cur->commentaire_titre,
							$cur->commentaire_contenu,
							$cur->page_id,
							$details);
			
			if($estIndesirable) {
				$cur->commentaire_statut = CP_COMMENTAIRE_SPAM;
				$cur->commentaire_spam = '';
				foreach($details as $k => $v) {
					$cur->commentaire_spam.= $k.'['.$v.']|';
				}
			}
		}
		
		$rs = $this->core->con->select(
			'SELECT MAX(commentaire_id) '.
			'FROM '.$this->core->prefix.'commentaires'
		);
	
		$cur->commentaire_id = (integer) $rs->f(0) + 1;
		$cur->commentaire_date = dt::str('%Y-%m-%d %H:%M:%S');

		# --COMPORTEMENT-- coreAvantCreerCommentaire
		$this->core->executerComportement('coreAvantCreerCommentaire', $cur);
		
		$cur->insert();
		
		# --COMPORTEMENT-- coreApresCreerCommentaire
		$this->core->executerComportement('coreApresCreerCommentaire', $cur);
		
		return $cur;
		
	}
	//@}
	
	
	
	

	/// @name Gestion des médias
	//@{
	
	
	/**
	Retourne des médias. <var>$params</var> est un
	tableau qui peut prendre les paramètres suivants:
	
	- media_id: (integer) filtre sur l'identifiant du média
	- media_dossier: filtre sur le nom de dossier du média
	- media_fichier: filtre sur le nom de fichier du média
	- media_type: filtre sur le type du média
	- media_titre: filtre sur le titre du média
	- media_url: permet de trouver un média d'après son URL (partie qui
		suit monsite.example/medias). Des filtres sur media_dossier
		et media_fichier sont créés automatiquement.
	- utilisateur_id: (integer) filtre sur l'identifiant de l'utilisateur associé au média
	
	- rechercher: mots-clés séparés par des espaces pour rechercher dans les champs media_titre et media_fichier
	- operateur: opérateur à placer entre les conditions (CP_OPERATEUR_AND ou CP_OPERATEUR_OR)
	- order: ORDER BY...
	- limit: paramètre limite
	
	@param	params	<b>array</b>	Paramètres
	@param	type	<b>integer</b>	Type de résultat à retourner: CP_GET_RS, CP_GET_STATIC ou CP_GET_COMPTEUR
	
	@return	<b>recordset</b>	Médias
	*/
	public function getMedias($params=array(), $type=CP_GET_RS)
	{

		$champs = array(
			array('media_id', 'M', CP_INTEGER),
			array('media_dossier', 'M', CP_STRING),
			array('media_fichier', 'M', CP_STRING),
			array('media_type', 'M', CP_STRING),
			array('media_titre', 'M', CP_STRING),
			array('media_description', 'M', CP_STRING),
			array('media_date_creation', 'M', CP_DATE),
			array('media_date_modification', 'M', CP_DATE),
			array('utilisateur_id', 'M', CP_INTEGER)
		);
		
		$from = $this->core->prefix."medias M";
		
		# URL
		if(!empty($params['media_url'])) {
			$dernier_slash = strrpos($params['media_url'], '/');
			$media_dossier = substr($params['media_url'], 0, $dernier_slash);
			$params['media_dossier'] = empty($media_dossier) ? '/' : $media_dossier;
			$params['media_fichier'] = substr($params['media_url'], $dernier_slash+1);			
		}

		# Rechercher
		if(!empty($params['rechercher'])) {
			
			$motsCle = cpText::splitWords($params['rechercher'], false);
			$rechercheSqlT = array();
			
			if(!empty($motsCle))
			{
			
				foreach($motsCle as $v)
				{
	
					if(!empty($v)) {
						$v = $this->core->con->escape('%'.$v.'%');
						$sqlRecherche =
						"( M.media_fichier LIKE '".$v."' OR ".
						"M.media_titre LIKE '".$v."' )";
						$rechercheSqlT[] = $sqlRecherche;
					}
					
				}
	
				$params['conditions_supp'] = array(' ( '.implode("\n AND ", $rechercheSqlT).' ) ');
			
			}

		}

		# --COMPORTEMENT-- coreAvantGetMedias
		$this->core->executerComportement('coreAvantGetMedias', array(&$champs, &$params, &$from, &$type));
		
		$strReq = $this->core->selectSql(
			$champs,		# Champs
			$params,		# Valeurs
			$from,			# From
			$type			# Type
		);

		$rs = $this->core->con->select($strReq);
		
		# --COMPORTEMENT-- coreApresGetMedias
		$this->core->executerComportement('coreApresGetMedias', array(&$champs, &$params, &$from, &$type), $rs);

		return $this->core->selectReturn($type, $rs, 'cpMedia');
		
	}
	
	
	/**
	Retourne un <b>array</b> contenant une liste de tous les
	dossiers de médias prêt à être utilisé par 'form::combo'.
	
	@return	<b>array</b>
	*/
	public function getComboDossiersMedias()
	{
		$combo = array('medias/' => '/');
		
		$d = new filemanager(CP_CHEMIN_MEDIAS);
		$this->etapeComboDossiersMedias($d, '/', $combo);

		return $combo;	
	}
	
	
	/** @ignore */
	protected function etapeComboDossiersMedias($d, $dossier, &$combo)
	{
		$d->chdir($dossier);
		$d->getDir();

		foreach($d->dir['dirs'] as $dir)
		{
			if($dir->parent != 1)
			{			
				$combo['medias/'.$dir->relname] = '/'.$dir->relname;
				$this->etapeComboDossiersMedias($d, $dir->relname, $combo);
			}
		}
	}
	
	
	/**
	Retourne le contenu du dossier de médias <var>$dossier</var> dans un
	tableau comportant les clés suivantes:
	- <var>dirs</var>	Tableau contenant un fileitem pour chaque sous-dossier
	- <var>medias</var>	Recordset de cpMedias correspondant aux médias du dossier
	
	@param	dossier	<b>string</b>	Chemin du dossier
	@param	params	<b>array</b>	Paramètres supplémentaires pour la recherche des médias
	
	@return	<b>array</b>	Contenu du dossier
	*/
	public function getDossierMedias($dossier, $params = array())
	{
		
		$dossierMedias = array();
		
		# Liste des sous-dossiers
		$filemanager = new filemanager(CP_CHEMIN_MEDIAS);
		$filemanager->chdir($dossier);
		$filemanager->getDir();
		$dossierMedias['dirs'] = $filemanager->dir['dirs'];
		
		if($dossier != '/') {
			array_shift($dossierMedias['dirs']);
		}

		# Liste des médias
		$params['media_dossier'] = $dossier;
		$medias = $this->getMedias($params,CP_GET_STATIC);

		# Synchronisation DB/fichiers: seulement s'il n'y a pas de critères de recherche	
		if(count($params) == 1)
		{
			
			$upd = false;
			
			$fichiersMedias = $filemanager->dir['files'];
			$fichiersMediasK = array();
			
			foreach($fichiersMedias as $v) {
				$fichiersMediasK[$v->basename] = $v;
			}
			
			# Supprimer de la DB les médias non présents dans
			# le dossier
			while($medias->fetch())
			{
				if(!isset($fichiersMediasK[$medias->media_fichier])) {
					try {
						$medias->supprimer();
					} catch(cpErreur $e) { }
					$upd = true;
				}
				else {
					unset($fichiersMediasK[$medias->media_fichier]);
				}
			}
			
			# Ajouter dans la base de données les médias présents
			# dans le dossier
			if(!empty($fichiersMediasK) && $this->core->auth->verifierPermission('medias'))
			{
								
				$cur = $this->core->con->openCursor($this->core->prefix.'medias');
			
				$rs = $this->core->con->select(
					'SELECT MAX(media_id) '.
					'FROM '.$this->core->prefix.'medias'
				);
				
				$media_id = (integer) $rs->f(0);
				
				$cur->media_dossier = $dossier;
				$cur->media_date_creation = dt::str('%Y-%m-%d %H:%M:%S');
				$cur->media_date_modification = dt::str('%Y-%m-%d %H:%M:%S');
				$cur->utilisateur_id = (integer) $this->core->auth->getUtilisateurID();
				
				foreach($fichiersMediasK as $fichier)
				{
					$media_id++;
					$cur->media_id = (integer) $media_id;
					$cur->media_fichier = $fichier->basename;
					$cur->media_type = $fichier->type;
					
					$dernierPoint = strrpos($fichier->basename, '.');
					
					if($dernierPoint === false) {
						$cur->media_titre = $fichier->basename;
					} else {
						$cur->media_titre = substr($fichier->basename, 0, $dernierPoint);
					}
					
					$cur->insert();
					
					$upd = true;
					
				}
	
			}
			
			if($upd) {
				$medias = $this->getMedias($params, CP_GET_STATIC);
			}
			
		}

		$dossierMedias['medias'] = $medias;
		$dossierMedias['media_dossier'] = $dossier;
		
		return $dossierMedias;
	
	}
	
	
	/**
	Crée un nouveau dossier de médias.
	
	<var>$formulaire</var> contient:
	- dossier_parent	Chemin du dossier dans lequel créer le nouveau dossier
	- dossier_nom		Nom du nouveau dossier
	
	@param	formulaire	<b>array</b>	Données de formulaire
	@return	<b>string</b>	Chemin vers le nouveau dossier
	*/
	public function creerDossierMedia($formulaire)
	{
		
		# Vérifier si le dossier parent existe
		if(!is_dir(CP_CHEMIN_MEDIAS.$formulaire['dossier_parent'])) {
			throw new cpErreur(__('Dossier invalide'), 'dossier_parent', 103);
		}
		
		# Vérifier si il est possible d'écrire dans le dossier parent
		if(!is_writable(CP_CHEMIN_MEDIAS.$formulaire['dossier_parent'])) {
			throw new cpErreur(__('Le dossier n\'est pas accessible en écriture.'), 'dossier_parent', 401);
		}
		
		# Le nom du dossier ne peut pas être vide
		if(empty($formulaire['dossier_nom'])) {
			throw new cpErreur(__('Vous devez indiquer un nom de dossier.'), 'dossier_nom', 101);
		}
		
		# Vérifier la présence de caractères indésirables dans le nom du dossier
		$count = 0;		
		$formulaire['dossier_nom'] = str_replace(array('?','&','#','=','+','<','>', '/', '.'),'',$formulaire['dossier_nom'], $count);
		if($count != 0) {
			throw new cpErreur(sprintf(__('Les caractères suivants sont interdits: %s'), '/ . ? & # = + < > .'), 'dossier_nom', 110);
		}
		
		$dossier = text::tidyURL($formulaire['dossier_nom'], false);

		# Ne pas crééer deux dossier avec le même nom!
		if(is_dir(CP_CHEMIN_MEDIAS.$formulaire['dossier_parent'].'/'.$dossier)) {
			throw new cpErreur(__('Ce nom de dossier est déjà utilisé.'), 'dossier_nom', 102);
		}
		
		# --COMPORTEMENT-- coreAvantCreerDossierMedia
		$this->core->executerComportement('coreAvantCreerDossierMedia', array(&$formulaire['dossier_parent'], &$dossier));
		
		$filemanager = new filemanager(CP_CHEMIN_MEDIAS);
		$filemanager->chdir($formulaire['dossier_parent']);
		$filemanager->makeDir($dossier);
		
		# --COMPORTEMENT-- coreApresCreerDossierMedia
		$this->core->executerComportement('coreApresCreerDossierMedia', array(&$formulaire['dossier_parent'], &$dossier));
		
		return $formulaire['dossier_parent'].'/'.$dossier;
		
	}
		
	
	/**
	Trouve un nom unique à donner à un nouveau média.
	Ex.: Si le fichier transféré a pour nom «exemple.jpg» et qu'un fichier
	sur le serveur porte déjà ce nom, cette méthode renverra «exemple-2.jpg».
	
	@param	dossier		<b>string</b>	Dossier de destination du média
	@param	fichier		<b>string</b>	Nom de fichier original du média

	@return	<b>string</b>	Nom de fichier unique
	*/
	private function trouverNouveauNomMedia($dossier, $fichier, $i=2)
	{
		$count = 0;
		$nouveauNom = preg_replace('/(\.[a-zA-Z0-9]+$)/', '-'.(integer) $i.'$1', $fichier, 1, $count);
		
		if($count == 0) {
			$nouveauNom = $fichier.'-'.(integer) $i;
		}

		if(!file_exists(path::clean(CP_CHEMIN_MEDIAS.$dossier.'/'.$nouveauNom))) {
			return $nouveauNom;
		} else {
			$i++;
			return $this->trouverNouveauNomMedia($dossier, $fichier, $i);
		}
	}
	
	
	/**
	Transfère un média sur le serveur, et crée un enregistrement dans
	la base de données.
	
	<var>$cur</var> contient:
	- media_dossier
	- media_titre
	- media_description
	
	@param	cur			<b>cursor</b>	Curseur du nouveau média
	@param	tmp			<b>string</b>	Nom du champ de formulaire contenant le média
	
	@return	<b>cursor</b>	Curseur contenant les informations du nouveau média
	*/
	public function transfererMedia($cur, $tmp)
	{
	
		files::uploadStatus($_FILES[$tmp]);
		
		$dossier = path::clean($cur->media_dossier);
		
		if(substr($dossier, 0, 1) != '/') {
			$dossier = '/'.$dossier;
		}

		# Vérifier si le dossier parent existe
		if(!is_dir(CP_CHEMIN_MEDIAS.$dossier)) {
			throw new cpErreur(__('Dossier invalide'), 'media_dossier', 103);
		}
		
		# Vérifier si il est possible d'écrire dans le dossier parent
		if(!is_writable(CP_CHEMIN_MEDIAS.$dossier)) {
			throw new cpErreur(__('Le dossier n\'est pas accessible en écriture.'), 'media_dossier', 401);
		}
		
		# Le titre du média ne doit pas être vide
		if($cur->media_titre == '') {
			throw new cpErreur(__('Vous devez indiquer un titre.'), 'media_titre', 101);
		}
		
		# Nettoyer le nom du fichier
		$fichier = files::tidyFileName($_FILES[$tmp]['name']);

		# Vérifier qu'il n'existe pas déjà un fichier avec ce nom
		if(file_exists(path::clean(CP_CHEMIN_MEDIAS.$dossier.'/'.$fichier))) {
			$fichier = $this->trouverNouveauNomMedia($dossier, $fichier);
		}

		# Curseur
		$rs = $this->core->con->select(
			'SELECT MAX(media_id) '.
			'FROM '.$this->core->prefix.'medias'
		);
		
		$cur->media_id = (integer) $rs->f(0) + 1;
		$cur->media_dossier = $dossier;
		$cur->media_fichier = $fichier;
		$cur->media_type = files::getMimeType($fichier);
		$cur->media_date_creation = dt::str('%Y-%m-%d %H:%M:%S');
		$cur->media_date_modification = dt::str('%Y-%m-%d %H:%M:%S');
		$cur->utilisateur_id = (integer) $this->core->auth->getUtilisateurID();
		
		# --COMPORTEMENT-- coreAvantTransfererMedia
		$this->core->executerComportement('coreAvantTransfererMedia', $cur, $tmp);
		
		# Transfert du fichier
		try {
			$filemanager = new filemanager(CP_CHEMIN_MEDIAS.$cur->media_dossier);
			$filemanager->uploadFile($_FILES[$tmp]['tmp_name'], $cur->media_fichier);
		} catch (Exception $e) {
			throw new cpErreur(__('Impossible de récupérer le fichier téléversé.'), $tmp, 410);
		}
		
		# Insertion dans la base de données
		$cur->insert();
		
		# --COMPORTEMENT-- coreApresTransfererMedia
		$this->core->executerComportement('coreApresTransfererMedia', $cur);
		
		return $cur;

	}
	//@}
	




	/// @name Gestion des gadgets
	//@{
	
	
	/**
	Retourne des gadgets. <var>$params</var> est un
	tableau qui peut prendre les paramètres suivants:
	
	- gadget_inst_id
	- theme_id
	- zone_id
	- gadget_id
	- gadget_inst_titre
	
	- operateur: opérateur à placer entre les conditions (CP_OPERATEUR_AND ou CP_OPERATEUR_OR)
	- order: ORDER BY...
	- limit: paramètre limite
	
	Note: Si vous choisissez une requête de type CP_GET_STATIC, la méthode
	renverra un <b>array</b> d'objets plutôt qu'un recordset.
	
	@param	params	<b>array</b>	Paramètres
	@param	type	<b>integer</b>	Type de résultat à retourner: CP_GET_RS, CP_GET_STATIC ou CP_GET_COMPTEUR
	
	@return	<b>recordset|array</b>	Gadgets
	*/
	public function getGadgets($params=array(), $type=CP_GET_STATIC)
	{
		
		$champs = array(
			array('gadget_inst_id', 		'G', CP_INTEGER),
			array('theme_id', 				'G', CP_STRING),
			array('zone_id', 				'G', CP_STRING),
			array('gadget_id', 				'G', CP_STRING),
			array('gadget_inst_conditions',	'G', CP_STRING),
			array('gadget_inst_titre',		'G', CP_STRING),
			array('gadget_inst_data',		'G', CP_STRING)
		);
		
		$from = $this->core->prefix."gadgets G";

		# --COMPORTEMENT-- coreAvantGetGadgets
		$this->core->executerComportement('coreAvantGetGadgets', array(&$champs, &$params, &$from, &$type));
		
		$strReq = $this->core->selectSql(
			$champs,		# Champs
			$params,		# Valeurs
			$from,			# From
			$type			# Type
		);
		
		$rs = $this->core->con->select($strReq);

		if($type == CP_GET_STATIC)
		{
			
			$gadgets = array();
			
			while($rs->fetch()) {
				$gadgets[] = $this->core->getObjetGadget($rs->gadget_id, $rs);
			}
			
			$rs = new cpStaticRecord($gadgets, null, $this->core);
			
		}
		
		# --COMPORTEMENT-- coreApresGetGadgets
		$this->core->executerComportement('coreApresGetGadgets', array(&$champs, &$params, &$from, &$type), $rs);
		
		return ($type == CP_GET_STATIC) ? $rs : $this->core->selectReturn($type, $rs, NULL);
		
	}
	//@}




	
	/// @name Gestion des groupes d'utilisateurs
	//@{
	
	
	/**
	Retourne des groupes d'utilisateurs. <var>$params</var> est
	un tableau qui peut prendre les paramètres suivants:
	
	- groupe_id
	- groupe_nom
	
	- operateur: opérateur à placer entre les conditions (CP_OPERATEUR_AND ou CP_OPERATEUR_OR)
	- order: ORDER BY...
	- limit: paramètre limite
	
	@param	params	<b>array</b>	Paramètres
	@param	type	<b>integer</b>	Type de résultat à retourner: CP_GET_RS, CP_GET_STATIC ou CP_GET_COMPTEUR
	
	@return	<b>recordset</b>	Groupes d'utilisateurs
	*/
	public function getGroupesUtilisateurs($params, $type=CP_GET_RS)
	{
		$champs = array(
			array('groupe_id', 'G', CP_INTEGER),
			array('groupe_nom', 'G', CP_STRING),
			array('groupe_permissions', 'G', CP_STRING)
		);
		
		$from = $this->core->prefix.'groupes_utilisateurs G';

		# --COMPORTEMENT-- coreAvantGetGroupesUtilisateurs
		$this->core->executerComportement('coreAvantGetGroupesUtilisateurs', array(&$champs, &$params, &$from, &$type));
		
		$strReq = $this->core->selectSql(
			$champs,		# Champs
			$params,		# Valeurs
			$from,			# From
			$type			# Type
		);
		 
		$rs = $this->core->con->select($strReq);

		# --COMPORTEMENT-- coreApresGetGroupesUtilisateurs
		$this->core->executerComportement('coreApresGetGroupesUtilisateurs', array(&$champs, &$params, &$from, &$type), $rs);
		
		return $this->core->selectReturn($type, $rs, 'cpGroupeUtilisateurs');
		
	}
	
	
	/**
	Crée un nouveau groupe d'utilisateurs.
	
	@param	groupe_nom	<b>string</b>	Nom du groupe d'utilisateurs
	@param	permissions	<b>array</b>	Permissions du groupe d'utilisateurs
	
	@return	<b>cpGroupeUtilisateurs</b>	Instance de cpGroupeUtilisateurs
	*/
	public function creerGroupeUtilisateurs($groupe_nom, $permissions)
	{
		
		# Le nom du groupe ne doit pas être vide
		if(empty($groupe_nom)) {
			throw new cpErreur(__('Vous devez indiquer un nom de groupe.'), 'groupe_nom', 101);
		}
		
		# Le nom du groupe doit être unique
		$checkNom = $this->core->site->getGroupesUtilisateurs(array(
			'groupe_nom' => $groupe_nom
		), CP_GET_COMPTEUR);
		if($checkNom->f(0) != 0) {
			throw new cpErreur(__('Ce nom de groupe est déjà utilisé.'), 'groupe_nom', 102);
		}
		
		# Identifiant
		$rs = $this->core->con->select(
			'SELECT MAX(groupe_id) '.
			'FROM '.$this->core->prefix.'groupes_utilisateurs'
		);
		
		# Curseur
		$cur = $this->core->con->openCursor($this->core->prefix.'groupes_utilisateurs');
		$cur->groupe_id = (integer) $rs->f(0) + 1;
		$cur->groupe_nom = $groupe_nom;
		$cur->groupe_permissions = serialize(cpGroupeUtilisateurs::parseFormulairePermissions($permissions, $this->core));
		
		# --COMPORTEMENT-- coreAvantCreerGroupeUtilisateurs
		$this->core->executerComportement('coreAvantCreerGroupeUtilisateurs', $cur);
		
		# Insertion dans la base de données
		$cur->insert();
		
		# --COMPORTEMENT-- coreApresCreerGroupeUtilisateurs
		$this->core->executerComportement('coreApresCreerGroupeUtilisateurs', $cur);
		
		# Retourner une instance de cpGroupeUtilisateurs
		$groupeUtilisateurs = $this->getGroupesUtilisateurs(array(
			'groupe_id' => (integer) $cur->groupe_id
		), CP_GET_STATIC);
		
		return $groupeUtilisateurs;
		
	}
	//@}
	
	
	
	

	/// @name Gestion des utilisateurs
	//@{
	
	
	/**
	Retourne des utilisateurs. <var>$params</var> est un
	tableau qui peut prendre les paramètres suivants:
	
	- utilisateur_id: (integer) filtre sur l'identifiant de l'utilisateur
	- utilisateur_superadmin: (integer) filtre sur le champ utilisateur_superadmin (0, 1)
	- groupe_id: (integer) filtre sur l'identifiant du groupe d'utilisateurs
	- utilisateur_login: filtre sur le nom d'utilisateur
	- utilisateur_prenom: filtre sur le prénom de l'utilisateur
	- utilisateur_nom: filtre sur le nom de l'utilisateur
	- utilisateur_pseudo: filtre sur le pseudonyme de l'utilisateur
	- utilisateur_email: filtre sur l'email de l'utilisateur
	- utilisateur_langue: filtre sur la langue de l'utilisateur
	- utilisateur_statut: (integer) filtre sur le statut de l'utilisateur (CP_UTILISATEUR_ACTIF, CP_UTILISATEUR_TEMP, CP_UTILISATEUR_DESACTIVE)
	- utilisateur_cle
	
	- operateur: opérateur à placer entre les conditions (CP_OPERATEUR_AND ou CP_OPERATEUR_OR)
	- order: ORDER BY...
	- limit: paramètre limite
	
	@param	params	<b>array</b>	Paramètres
	@param	type	<b>integer</b>	Type de résultat à retourner: CP_GET_RS, CP_GET_STATIC ou CP_GET_COMPTEUR
	@return	<b>recordset</b>	Utilisateurs
	*/
	public function getUtilisateurs($params=array(), $type=CP_GET_RS)
	{
		
		$champs = array(
			array('utilisateur_id', 'U', CP_INTEGER),
			array('utilisateur_superadmin', 'U', CP_INTEGER),
			array('groupe_id', 'U', CP_INTEGER),
			array('utilisateur_login', 'U', CP_STRING),
			array('utilisateur_mot_de_passe', 'U', CP_STRING),
			array('utilisateur_prenom', 'U', CP_STRING),
			array('utilisateur_nom', 'U', CP_STRING),
			array('utilisateur_pseudo', 'U', CP_STRING),
			array('utilisateur_email', 'U', CP_STRING),
			array('utilisateur_langue', 'U', CP_STRING),
			array('utilisateur_preferences', 'U', CP_STRING),
			array('utilisateur_statut', 'U', CP_INTEGER),
			array('utilisateur_cle', 'U', CP_STRING),
			array('utilisateur_date', 'U', CP_DATE),
			array('utilisateur_date_modification', 'U', CP_DATE)
		);
		
		$from = $this->core->prefix."utilisateurs U";
		
		if($params['avec_groupe'] == true)
		{
			$champs = array_merge($champs, array(
				array('groupe_nom', 'G', CP_STRING)
			));
			
			$from.="\nLEFT OUTER JOIN ".$this->core->prefix.'groupes_utilisateurs G ON U.groupe_id = G.groupe_id';

		}

		# --COMPORTEMENT-- coreAvantGetUtilisateurs
		$this->core->executerComportement('coreAvantGetUtilisateurs', array(&$champs, &$params, &$from, &$type));
		
		$strReq = $this->core->selectSql(
			$champs,		# Champs
			$params,		# Valeurs
			$from,			# From
			$type			# Type
		);
		
		$rs = $this->core->con->select($strReq);

		# --COMPORTEMENT-- coreApresGetUtilisateurs
		$this->core->executerComportement('coreApresGetUtilisateurs', array(&$champs, &$params, &$from, &$type), $rs);
		
		return $this->core->selectReturn($type, $rs, 'cpUtilisateur');

	}
	
	
	/**
	Vérifie la validité des données d'un curseur d'utilisateur.
	
	@param	cur				<b>cursor</b>	Curseur d'utilisateur
	@param	nouveau			<b>bool</b>		Indique s'il s'agit d'un nouvel utilisateur
	@param	utilisateur_id	<b>integer</b>	Identifiant de l'utilisateur, si ce n'est pas un nouvel utilisateur
	*/
	public function verifierDonneesUtilisateur($cur, $nouveau=false, $utilisateur_id=0)
	{
		
		if($cur->isField('utilisateur_login'))
		{
			# Le nom d'utilisateur ne doit pas être vide
			if($cur->utilisateur_login == '') {
				throw new cpErreur(__('Vous devez indiquer un nom d\'utilisateur.'), 'utilisateur_login', 101);
			}
	
			# Le nom d'utilisateur doit avoir entre 2 et 64 caractères
			if(strlen($cur->utilisateur_login) < 2 || strlen($cur->utilisateur_login) > 64) {
				throw new cpErreur(__('Le nom d\'utilisateur doit contenir entre 2 et 64 caractères.'), 'utilisateur_login', 111);
			}
	
			# Le nom d'utilisateur doit être unique
			$params = array('utilisateur_login' => $cur->utilisateur_login);
			if(!$nouveau) {
				$params['utilisateur_id'] = array('!=' => array((integer) $utilisateur_id));
			}
			$loginCheck = $this->getUtilisateurs($params, CP_GET_COMPTEUR);
			if($loginCheck->f(0) != 0) {
				throw new cpErreur(__('Ce nom d\'utilisateur est déjà utilisé.'), 'utilisateur_login', 102);
			}
		}

		# Le pseudonyme ne doit pas être vide
		if($cur->isField('utilisateur_pseudo') && $cur->utilisateur_pseudo == '') {
			throw new cpErreur(__('Vous devez indiquer un pseudonyme.'), 'utilisateur_pseudo', 101);
		}

		# Vérification de l'email		
		if($cur->utilisateur_email != '' && !text::isEmail($cur->utilisateur_email)) {
			throw new cpErreur(__('Adresse électronique invalide.'), 'utilisateur_email', 105);
		}
		
		# Vérification de utilisateur_superadmin
		if($cur->isField('utilisateur_superadmin') && !in_array($cur->utilisateur_superadmin, array(0, 1))) {
			throw new cpErreur(__('Donnée invalide'), 'utilisateur_superadmin', 103);
		}
		
		# La langue doit exister
		if($cur->isField('utilisateur_langue') && !array_key_exists($cur->utilisateur_langue, $this->core->getLangues())) {
			throw new cpErreur(__('Donnée invalide'), 'utilisateur_langue', 103);
		}
		
		# Vérification du statut
		if($cur->isField('utilisateur_statut') && !in_array($cur->utilisateur_statut, array(CP_UTILISATEUR_ACTIF, CP_UTILISATEUR_DESACTIVE, CP_UTILISATEUR_TEMP))) {
			throw new cpErreur(__('Donnée invalide'), 'utilisateur_statut', 103);
		}
		
	}
	
	
	/**
	Crée une nouvel utilisateur.
	
	<var>$cur</var> contient:
	- utilisateur_superadmin
	- groupe_id
	- utilisateur_login
	- utilisateur_mot_de_passe
	- utilisateur_prenom
	- utilisateur_nom
	- utilisateur_pseudo
	- utilisateur_email
	- utilisateur_langue
	- utilisateur_statut
	- utilisateur_cle
	
	@param	cur	<b>cursor</b>	Curseur d'utilisateur
	
	@return	<b>cpUtilisateur</b>
	*/
	public function creerUtilisateur($cur)
	{

		# Le mot de passe est obligatoire
		if($cur->utilisateur_mot_de_passe == '') {
			throw new cpErreur(__('Vous devez indiquer un mot de passe.'), 'utilisateur_mot_de_passe_1', 101);
		}
		
		# Dates
		$cur->utilisateur_date				= dt::str('%Y-%m-%d %H:%M:%S');
		$cur->utilisateur_date_modification	= dt::str('%Y-%m-%d %H:%M:%S');
		
		# Vérification des données
		$this->verifierDonneesUtilisateur($cur, true);
		
		# Identifiant
		$rs = $this->core->con->select(
			'SELECT MAX(utilisateur_id) '.
			'FROM '.$this->core->prefix.'utilisateurs'
		);
		
		# Curseur
		$cur->utilisateur_id = 			(integer) $rs->f(0) + 1;
		
		if(!$cur->isField('utilisateur_preferences')) {
			$cur->utilisateur_preferences = serialize(array(
				'format' => CP_FORMAT_XHTML
			));
		}
		
		# --COMPORTEMENT-- coreAvantCreerUtilisateur
		$this->core->executerComportement('coreAvantCreerUtilisateur', $cur);
		
		$cur->insert();
		
		# --COMPORTEMENT-- coreApresCreerUtilisateur
		$this->core->executerComportement('coreApresCreerUtilisateur', $cur);
		
		# Retourner une instance de cpUtilisateur
		$utilisateur = $this->getUtilisateurs(array(
			'utilisateur_id' => (integer) $cur->utilisateur_id
		), CP_GET_STATIC);
		
		return $utilisateur;
		
	}
	//@}
	
	
}

?>